Clean Code - Chapter 9 单元测试

2017-07-06 01:23

作者:给立乐*
出处:http://spencer-dev.com/2017/07/06/Clean Code - Chapter 9 单元测试
声明:本文采用以下协议进行授权: 自由转载-非商用-非衍生-保持署名|Creative Commons BY-NC-ND 3.0 ,转载请注明作者及出处。

单元测试

过去十年以来,编程专业领域进步很大。1997年时,没人听说过测试驱动开发(TDD)。对于我们之中的大多数人来说,单元测试是那种用来确保程序“可运行”的用过即扔的短代码。我们辛勤地编写类和方法,在弄出一些特殊代码来测试它们。通常这会是种简单的驱动式程序,让我们能够手工与自己编写的程序交互。

如今,我会编写测试,确保代码中每个犄角旮旯都如我所愿的工作。敏捷和 TDD 运动鼓舞了许多程序员编写自动化单元测试,每天还有更多人加入这个行列。但是,在争先恐后将测试加入规程中时,许多程序员遗漏了一些关于编写好测试的更细微但却更重要的点。

TDD 三定律

谁都知道 TDD 要求我们在编写生产代码前先编写单元测试。但这条规则只是冰山之巅。看看下列三定律:

  • 定律一,在编写不能通过的单元测试前,不可编写生产代码。
  • 定律二,只可编写刚好无法通过的单元测试,不能编译也不能通过。
  • 定律三,只可编写刚好通过当前失败测试的生产代码。

这样三条定律将限制你在大概 30秒 一个的循环中。测试与生产代码一起编写,测试纸笔生产代码早写几秒钟。

这样写程序,我们每天就会编写数十个测试,每个月编写数百个测试,每年编写数千个测试。这样写程序,测试将覆盖所有生产代码。测试代码量足以匹敌生产代码量,令导致令人生畏的管理问题。

保持测试整洁

有团队认为,测试代码的维护不应遵循生产代码的质量标准。他们彼此默许在单元测试中破坏规矩。“速而不周”成了团队格言。变量名用不好,测试函数不必短小和具有描述性。测试代码不必做良好设计和仔细划分。只要测试代码还能工作,只要覆盖着生产代码,这就足够好。

从编写那种用后即扔的测试到编写全套自动化单元测试是一大进步。所以,就像那个团队一样,认为脏测试好过没测试。

但这个团队没意识到的是,脏测试同于没测试,或者更甚。问题在于,测试必须随生产代码的演进而修改。测试越脏,就越难修改。测试代码越缠结,你就越有可能花更多时间塞进新测试,而不是编写新生产代码。修改生产代码后,旧测试就会开始失败,而测试代码中乱七八糟的东西将阻碍代码再次通过。于是测试变得就像是不断翻番的债务。

随着版本递进,团队维护测试代码的代价也在上升。最终,它变成了开发者最大的抱怨对象。当经理们问及为何超支如此巨大,开发者们就归结于测试。最后他们只能扔掉整个测试代码。

但是,没有了测试代码,他们就失去了确保对代码的改动能如愿工作的能力。没有了测试代码,他们就无法确保对系统某个部分的修改不会影响到系统的其他部分。故障率开始增加。随着并非出自有意的故障越来越多,他们开始害怕做改动。他们不在清理生产代码,因为他们害怕修改带来的损害多与收益。生产代码开始腐坏。最后,他们只剩下没有测试、纷乱而缺陷缠身的生产代码,沮丧的客户,还有对测试的失望。

在某种意义上,他们说对了。测试的确让他们失望。不过是他们自己决定让测试变得乱七八糟,而那正是失败的根源。

如果他们保持测试的整洁,测试就不会令他们失望。

上面的事情说明,测试代码和生产代码一样重要。它需要被思考,被设计和被照料。它该像生产代码一般保持整洁。

如果测试不能保持整洁,你就会失去它们。没有了测试,你就会失去保证生产代码可拓展的一切要素。你没看错。正是单元测试让你的代码可拓展、可维护、可复用。原因很简单。有了测试,你就不担心对代码的修改!没有测试,每次修改都可能带来缺陷。无论架构多有拓展性,无论设计划分的有多好,没有了测试,你就很难做改动,因为你担忧改动会引入不可预知的缺陷。

有了测试,愁云一扫而空。测试覆盖率越高,你就越不担心。哪怕是对于那种架构并不优秀、设计晦涩纠缠的代码,你也能近乎没有后患的做修改。实际上,你能毫无顾忌地改进架构和设计!所以,覆盖了生产代码的自动化单元测试能尽可能地保持设计和架构的整洁。测试带来了一切好处,因为测试使改动变得可能。

如果测试不干净,你该懂自己代码的能力就有所牵制,而你也会开始失去改进代码结构的能力。测试越脏,代码就会变得越脏。最终,你丢失了测试,代码开始腐坏。

整洁的测试

整洁的测试有什么要素?有三个要素:可读性,可读性和可读性。在单元测试中,可读性甚至比生产代码中还重要。测试如何才能做到可读?和其他代码中一样:明确,简洁,还有足够的表达力。在测试中,你要以尽可能少的文字表达大量的内容。

所以, 最佳规则也许是应该尽可能减少每个概念的断言数量,每个测试函数只测试一个概念。

F.I.R.S.T

整洁的测试还遵循以下 5 条规则,这 5 条规则的首字母构成了本节标题:

  • 快速(Fast),测试应该够快。测试应该能快速运行。测试运行缓慢,你就不会想要频繁的运行它。如果你不频繁的运行测试,就不能尽早发现问题,也无法轻易修正,从而也不能轻而易举地清理代码。最终,代码就会腐坏。
  • 独立(Independent),测试应该互相独立。某个测试不应为下一个测试设定条件。你应该可以独立运行每个测试,以及任何顺序运行测试。当测试互相依赖时,头一个没通过就会导致一连串的测试失败,是问题诊断变得困难,隐藏了下级错误。
  • 可重复(Repeatable),测试应当可在任何环境中重复通过。你应该能够在生产环境、质检环境中运行测试,也能够在无网络的列车上用笔记本电脑运行测试。如果测试不能在任意环境中重复,你就总会有个解释其失败的借口。当环境条件不具备时,你也会无法运行测试。
  • 自足验证(Self-Validating),测试应该有布尔值输出。无论是通过或失败,你不应该查看日志文件来确认测试是否通过。你不应该手工对比两个不同文本文件来确认测试是否通过。如果测试不能自足验证,对失败的判断就会变的依赖主观,而运行测试也需要更长的手工操作时间。
  • 及时(Timely),测试应及时编写。单元测试应该恰好在使其通过的生产代码之前编写。如果在编写生产代码之后编写测试,你会发现生产代码难以测试。你可能会认为某些生产代码本身难以测试。你可能不会去设计可测试的代码。

小结

我们只是触及了这个话题的表面。实际上,我认为应该为整洁的测试写上一整本书。对于项目的健康度,测试和生产代码同等重要。或许测试更为重要,因为他保证和增强了生产代码的可拓展性、可维护性和可复用性。所以,保持测试整洁吧。让测试具有表达力并短小精悍。发明作为面向特定领域语言的测试 API ,帮助自己编写测试。

如果你坐视测试腐坏,那么代码也会跟着腐坏。保持测试整洁吧。


Comments: